#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
   File Name：      base_page.py
   Remark :         元素定位的基类，基类封装webdriver方法, 方便调用, 减少代码重复
-------------------------------------------------
"""


import time
import allure

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.remote.webelement import WebElement
from selenium.common.exceptions import *
from selenium.common.exceptions import TimeoutException

from hellchin.util.logger import log

from guitesting.common.file.operation_yaml import ReadYaml
# from guitesting.common.util.log_process import LogProcess
# from guitesting.common.util.logs import Log
from guitesting.common.util.return_time import ReturnTime
from guitesting.common.DriverManager.webdriver_controller import DriverTool


class BasePage(object):
    """
    1. 元素定位的基类（优化中）
    接收driver，为了让driver后续完全脱手，再也不接触driver而写，让测试者能够拜托driver的繁琐操作。
    """
    __img_dir = LogProcess().get_log_dir()[1]
    conf: dict = ReadYaml().get_every_config("SELENIUM_CONFIG")

    # driver = DriverTool.get_driver()

    def __init__(self):
        """
        初始化方法，用于接收WebDriver对象。

        说明：
            该方法设计目的是为了让测试者能够拜托WebDriver的繁琐操作，后续所有的浏览器操作都将通过封装后的方法进行，不再直接操作WebDriver对象。
        """
        print("元素定位的基类初始化")
        # self.driver = self.__class__.driver
        self.driver = DriverTool.get_driver()
        # 显示等待设置
        self.wait: WebDriverWait = WebDriverWait(
            driver=self.driver,
            timeout=self.conf.get("WAIT_ELEMENT"),
            poll_frequency=self.conf.get("POLL_ELEMENT")
        )

        # 返回实例，方便调用元素基类的方法
        # self.other = self

    def get_url(self, url: str) -> None:
        """
        进入指定连接
        :param url: 链接地址
        :return: None
        """
        self.driver.get(url=url)

    def get_current_url_path(self) -> str:
        """
        获取当前页面的url的路径
        :return: url路径
        """
        current_url = self.driver.current_url
        return current_url

    def set_img_error(self) -> None:
        """
        用例执行失败截图,并且加入allure测试报告中
        :return: 无返回值
        """
        # 获取图片存储的文件夹
        __time_tag = ReturnTime.get_Timestamp()
        __img_path = self.__img_dir + f"/{__time_tag}.png"
        try:
            # 进行截图
            self.driver.save_screenshot(filename=__img_path)
            log.error(f"截图成功文件名称为：{__time_tag}.png")
            __file = open(__img_path, "rb").read()
            allure.attach(__file, "用例执行失败截图", allure.attachment_type.PNG)
        except Exception as e:
            log.error(f"执行失败截图未能正确添加进入测试报告:{e}")
            raise e

    def set_img_case(self) -> None:
        """
        用例执行完毕截图，并且将截图加入allure测试报告中
        :return: 无返回值
        """
        with allure.step("关键步骤截图"):
            # 获取当前时间戳作为图片名称
            __img_name = ReturnTime.get_Timestamp()
            # 拼接图片保存路径
            __img_path = self.__img_dir + f"/{__img_name}.png"
            try:
                # 截图前等待1秒防止图片没有正常加载
                time.sleep(1)
                # 保存当前页面截图到指定路径
                self.driver.save_screenshot(filename=__img_path)
                # 打印日志，表示截图成功
                log.debug(f"用例执行完成，截图成功，文件名称为{__img_name}.png")

                # 读取图片信息
                __file = open(file=__img_path, mode="rb").read()
                # 将图片信息添加到allure测试报告中
                allure.attach(__file, "关键步骤截图", allure.attachment_type.PNG)
            except Exception as e:
                # 打印错误日志，表示截图并添加到测试报告失败
                log.error(f"测试结果截图，未能正确添加进入测试报告:{e}")
                raise e

    def find_element(self, locator: tuple, *, mode: str = "visible", model: str = None):
        """
        定位元素，支持所有定位单个元素的定位表达式
        :param model: 传参定位的是哪个页面 字符串形式
        :param locator: 元素的定位表达式 例:(By.xx,'定位表达式')
        :param mode: visible(元素可见), exist(元素存在)
        :return: 定位到的元素对象
        """
        # 选择等待方法，检测元素是否存在
        self.__select_wait_method(model=model, locator=locator, mode=mode)
        try:
            log.debug(f"正在定位{model}页面的: {locator} 的元素")
            element = self.driver.find_element(*locator)
            # 将被操作的元素染色
            self.element_dyeing(element=element)
            return element
        except TimeoutException:
            log.error(f"页面:{model},定位元素:{locator}定位超时")
            self.set_img_error()
            raise TimeoutException("元素定位超时请检查")
        except Exception:
            log.error(f"页面:{model},定位元素:{locator}定位失败")
            self.set_img_error()
            raise Exception("元素定位失败请检查")

    def find_elements(self, locator: tuple, *, mode: str = "visible") -> list:
        """
        定位一组元素，返回一个列表
        :param locator: 元素的定位表达式 例:(By.xx,'定位表达式')
        :param mode: visible(元素可见),notvisible(元素消失不可见), exist(元素存在)
        :return: 返回一组元素是一个列表
        """
        model = self.get_current_url_path()
        self.__select_wait_method(model=model, locator=locator, mode=mode)
        try:
            log.debug(f"正在定位{model}页面的: {locator} 的元素")
            element_list = self.driver.find_elements(*locator)
            return element_list
        except Exception as e:
            log.error(f"页面:{model},定位元素:{locator}定位失败")
            self.set_img_error()
            raise e

    def element_dyeing(self, element) -> None:
        """
        将被操作的元素染色
        :rollback: 是否将元素回滚
        :return: None
        """
        try:
            self.driver.execute_script(
                "arguments[0].setAttribute('style', 'background: yellow; border: 2px solid red;');",
                element)
        except Exception as e:
            log.info(f"无法染色染色失效：{e}")
            pass

    def __select_wait_method(self, model: str, locator: tuple, *, mode: str = "visible") -> None:
        """
        选择元素定位的等待方式
        :param model: 传参定位的是哪个页面 字符串形式
        :param locator: 元素的定位表达式 例:(By.xx,'定位表达式')
        :param mode: visible(元素可见), exist(元素存在),click(元素可点击)
        :return: 无返回值
        """
        if mode == "visible":
            self.__wait_element_visible(model=model, locator=locator)
        elif mode == "exist":
            self.__wait_element_exit(model=model, locator=locator)
        elif mode == "click":
            self.__wait_element_clickable(model=model, locator=locator)
        else:
            log.error(f"定位{model}页面的元素:{locator},mode参数传值异常,入参值为：{mode}")

    def __wait_element_visible(self, model: str, locator: tuple) -> None:
        """
        等待元素可见
        :param model: 元素所处的页面
        :param locator: 元素定位表达式
        :return: 无返回值
        """
        log.debug(f"-开始-等待页面{model}的元素：{locator}可见")
        try:
            # 获取等待开始时间的时间戳
            __start_time = ReturnTime.get_Timestamp()
            self.wait.until(method=ec.visibility_of_element_located(locator))

            # 计算元素等待的时间
            __wait_time = ReturnTime.get_Timestamp() - __start_time
            log.debug(f"页面：{model}上的元素{locator}已可见，共计等待{__wait_time:0.2f}秒")

        except TimeoutException:
            log.error(f"页面：{model},等待元素{locator}超时")
            self.set_img_error()
            raise TimeoutException('元素等待超时')

        except InvalidSelectorException as e:
            log.error(f"页面:{model},元素不可见或定位表达式:{locator}异常\n {e}")
            raise InvalidSelectorException('元素定位异常')

    def __wait_element_exit(self, model: str, locator: tuple) -> None:
        """
        等待元素存在
        :param model: 元素所处的页面
        :param locator: 元素定位表达式
        :return: 无返回值
        """
        log.debug(f"-开始-等待页面{model}的元素：{locator}存在")
        try:
            # 获取等待开始时间的时间戳
            __start_time = ReturnTime.get_Timestamp()
            self.wait.until(ec.presence_of_element_located(locator))
            # 计算元素等待的时间
            __wait_time = ReturnTime.get_Timestamp() - __start_time
            log.debug(f"页面：{model}上的元素{locator}已存在，共计等待{__wait_time:0.2f}秒")
        except TimeoutException as e:
            log.error(f"页面：{model},等待元素{locator}超时")
            self.set_img_error()
            raise e
        except InvalidSelectorException as e:
            log.error(f"页面:{model},元素不存在或定位表达式:{locator}异常\n {e}")
            raise e

    def __wait_element_clickable(self, model: str, locator: tuple) -> None:
        """
        等待元素点击
        :param model: 元素所处的页面
        :param locator: 元素定位表达式
        :return: 无返回值
        """
        log.debug(f"-开始-等待页面{model}的元素：{locator}可点击")
        try:
            # 获取等待开始时间的时间戳
            __start_time = ReturnTime.get_Timestamp()
            self.wait.until(ec.element_to_be_clickable(locator))
            # 计算元素等待的时间
            __wait_time = ReturnTime.get_Timestamp() - __start_time
            log.debug(f"页面：{model}上的元素{locator}已可点击，共计等待{__wait_time:0.2f}秒")
        except TimeoutException as e:
            log.error(f"页面：{model},等待元素{locator}超时")
            self.set_img_error()
            raise e
        except InvalidSelectorException as e:
            log.error(f"页面:{model},元素不可点击或定位表达式:{locator}异常\n {e}")
            raise e

    def wait_element_not_visible(self, model: str, locator: tuple) -> None:
        """
        等待元素不可见
        :param model: 元素所处的页面
        :param locator: 元素定位表达式
        :return: 无返回值
        """
        log.debug(f"-开始-等待页面{model}的元素：{locator}存在")
        try:
            # 获取等待开始时间的时间戳
            __start_time = ReturnTime.get_Timestamp()
            time.sleep(1)
            self.wait.until(ec.invisibility_of_element_located(locator))
            # 计算元素等待的时间
            __wait_time = ReturnTime.get_Timestamp() - __start_time
            log.debug(f"页面：{model}上的元素{locator}已经不可见，共计等待{__wait_time:0.2f}秒")
        except TimeoutException as e:
            log.error(f"页面：{model},等待元素{locator}不可见超时")
            self.set_img_error()
            raise e
        except InvalidSelectorException as e:
            log.error(f"页面:{model},元素不存在或定位表达式:{locator}异常\n {e}")
            raise e

    def move_element_visible(self, model: str, locator: tuple, element: WebElement, *, alignment: bool = False) -> None:
        """
         将元素移动到页面可见区域
        :param model: 传参定位的是哪个页面 字符串形式
        :param locator: 元素的定位表达式 例:(By.xx,'定位表达式')
        :param alignment 默认对其方式是元素和当前页面的底部对齐，可以传 alignment=''表示和顶部对齐
        :param element 需要可见的元素
        :return: 无返回值
        """
        log.debug(f'将{model}页面的元素:{locator}移动至浏览器可见区域')
        try:
            # 代码执行比页面渲染速度快 这里放0.5秒等待页面渲染
            time.sleep(0.5)
            self.driver.execute_script('arguments[0].scrollIntoView({0});'.format(alignment), element)
            # 休眠1秒让页面可以正常滚动到对应的位置再执行下去
            time.sleep(1)
        except Exception as e:
            log.error(f"{model}页面的元素:{locator}移动失败\n{e}")
            self.set_img_error()
            raise e

    def get_handles(self):
        """
        获取当前页面的句柄
        :return: 方法返回值当前获取到的句柄值
        """
        # 获取当前页面的句柄
        handles = self.driver.window_handles
        return handles

    def swich_window(self, old_handle) -> None:
        """
        浏览器页面切换--通过切换句柄实现切换到正在使用的页面上
        :param old_handle: 传入之前获取的句柄的值
        :return: None
        """
        # 等待最新的窗口出现
        self.wait.until(ec.new_window_is_opened(old_handle))
        # 调用获取句柄的方法拿到最新打开的标签页的句柄
        new_handle = self.get_handles()
        # 切换到最新页面因为时最新所以直接使用下标 -1 就行
        self.driver.switch_to.window(new_handle[-1])

    def refresh_page(self) -> None:
        """
        刷新当前页面
        :return: None
        """
        log.info(f"刷新页面: {self.get_current_url_path()}")
        self.driver.refresh()


